
/*
 * valgrind --track-fds=yes --leak-check=full --undef-value-errors=yes ./keysvr
 */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <termios.h>
#include <unistd.h>
#include <getopt.h>

#include <openssl/rsa.h>
#include <openssl/pem.h>

#include <event2/event.h>
#include <event2/util.h>
#include <event2/listener.h>
#include <event2/buffer.h>
#include <event2/bufferevent.h>

#include <sys/socket.h>

#include "cmd.h"
#include "utils.h"

#define KEY_SERVER_PORT 8889
#define KEY_SERVER_BACKLOG 10

#define COMMUNITY_RSA_PRIVATE_KEY_NAME	"kcommpriv.key"
static char kcomm_privkey_path[1024];

//#define UNIT_TEST
//#define SERIAL_TRANSPORT
#define ETHERNET_TRANSPORT
//#define TOKEN_BASE_ENCODE


#ifdef UNIT_TEST
/* 
 * this token is embedded in token which is encrypted 
 * by kcomm private key 
 */
#define KUSR_RSA_PUBLIC_KEY_NAME  "kusrpub.key"
#endif

#define TOKEN_FILE_NAME  "token.dat"
#define DEFAULT_SERIAL_PORT "/dev/ttyS0"


RSA *kcomm_privkey;       // USB dongle private key
static uint8_t serial_rsp_buf[2048];
/*
 * req format: TLV
 *
 *
 * +---------+---------+-----------+
 * | T_USRID | L_USRID | 0x20 data |
 * +---------+---------+-----------+
 * | T_PUBKEY| L_PUBKEY|  pub key  |
 * +---------+---------+-----------+
 * | T_WMAC  |    8    | wlan mac  |
 * +---------+---------+-----------+
 * | T_BMAC  |	   8	|   bt mac  |
 * +---------+---------+-----------+
 *
 */

enum auth_status {
	AUTH_ST_QUEUED = 0x01,
	AUTH_ST_KEY_GEND,
	AUTH_ST_KEY_REMOVED,
};

/* global context */
struct auth_context {
	struct event_base *base;
	struct bufferevent *serial_bev;
	int serial_fd;
};

struct request {
	struct request *next;

	uint8_t *pubkey;
	int pubkey_len;
	uint8_t imei[IMEI_LEN];
	uint8_t wmac[WIFI_ADDR_LEN];
	uint8_t bmac[BT_ADDR_LEN];
	uint32_t userid;
	uint32_t commid;	//pc acks
	uint32_t *auth_doorid;	// pc acks
	uint32_t *deauth_doorid;	// pc acks
	int num_auth, num_deauth;

	int status;

	struct bufferevent *bev;

#define MAX_REQUEST_BUFSZ	1024
	uint8_t buffer[MAX_REQUEST_BUFSZ];

    uint8_t *token_bin;
    int token_bin_len;
};

struct request *request_list;


struct request *find_request(uint32_t userid)
{
	struct request *p;

	for (p = request_list; p; p = p->next) {
		dbg("%s: list userid %08X, param user_id %08X", 
				__func__, p->userid, userid);
		if (p->userid == userid)
			return p;
	}

	return NULL;
}

void add_to_request_list(struct request *req)
{
	if (!request_list) {
		request_list = req;
		req->next = NULL;
	} else {
		req->next = request_list;
		request_list = req;
	}
}

void delete_from_request_list(struct request *req)
{
	struct request **p;

	for (p = &request_list; *p; p = &(*p)->next)
		if (*p == req) {
			*p = req->next;
			break;
		}
}


#if defined(SERIAL_TRANSPORT) || defined(ETHERNET_TRANSPORT)
int get_user_pub_key(struct request * req)
{
	return 0;
}

#elif defined(UNIT_TEST)

int get_user_pub_key(struct request *req)
{
	BIO *bio_pub;

	bio_pub = BIO_new_file(KUSR_RSA_PUBLIC_KEY_NAME, "rb");
	if (!bio_pub) {
		err("open file %s failed", KUSR_RSA_PUBLIC_KEY_NAME);
		return 1;
	}
	req->pubkey = malloc(1024);
	req->pubkey_len = BIO_read(bio_pub, req->pubkey, 1024);
	err("pubkey size %d", req->pubkey_len);

	BIO_free(bio_pub);

	return 0;
}
#endif

/**
 * Generate a token.
 *
 * Generate a token, and save its data in @token_bio.
 */
int generate_token(struct request *req, uint32_t doorid)
{
	unsigned char *buffer, *p, *buffer_encrypt = NULL;
	int flen, total_len, icount;
	int ret;
	struct auth_token *token;
	BIO *token_bio;
#ifdef TOKEN_BASE_ENCODE
	EVP_ENCODE_CTX ectx;
	uint8_t *token_in;
	int out, normal_len;
#endif

    token_bio = BIO_new(BIO_s_mem());

    buffer = calloc(1, sizeof(struct auth_token) + AUTH_TOKEN_MAX_PUB_LEN);
    if (!buffer) {
		err("Out-of-Memory: %s %d", __func__, __LINE__);
		ret = 1;
		goto out;
	}

	token = (struct auth_token *)buffer;
	token->bType = AUTH_TOKEN_TYPE;
	token->dwCommID = req->commid;
	token->dwDoorID = doorid;
    token->dwUserID = req->userid;
    memcpy(token->strBTAddress, req->bmac, sizeof(req->bmac));
    memcpy(token->strMACAddress, req->wmac, sizeof(req->wmac));
    memcpy(token->strIMEI, req->imei, sizeof(req->imei));
	get_user_pub_key(req);
	memcpy(token->strUserPub, req->pubkey, req->pubkey_len);

    total_len = sizeof(struct auth_token) + req->pubkey_len;

	dump(__func__, token, total_len);

	// encrypt token by using server private key
	flen = RSA_size(kcomm_privkey);
	icount = flen - 11;	// input block size
	buffer_encrypt = malloc(flen);	// output block size
	if (!buffer_encrypt) {
		err("Out-of-Memory: %s %d", __func__, __LINE__);
		ret = 1;
		goto out;
	}

	for (p = buffer; total_len > 0; total_len -= icount, p += icount) {
		if (total_len < icount)
			icount = total_len;
		if (!icount)
			break;
		ret = RSA_private_encrypt(icount, p, buffer_encrypt, kcomm_privkey, RSA_PKCS1_PADDING);
		if (ret <= 0) {
			err("encrypt failed: %s %d", __func__, __LINE__);
			ret = 1;
			goto out;
		}
		BIO_write(token_bio, buffer_encrypt, ret);
	}


#ifndef TOKEN_BASE_ENCODE
	req->token_bin_len = BIO_number_written(token_bio);
	req->token_bin = malloc(req->token_bin_len);
	if (!req->token_bin) {
		err("Out-of-Memory: %s %d", __func__, __LINE__);
		ret = 1;
		goto out;
	}
	BIO_read(token_bio, req->token_bin, req->token_bin_len);

	dump("token dump", req->token_bin, req->token_bin_len);
#else /* TOKEN_BASE_ENCODE */
	EVP_EncodeInit(&ectx);
	total_len = out = 0;
	normal_len = BIO_number_written(token_bio);

	token_in = calloc(1, normal_len);
	if (!token_in) {
		err("Out-of-Memory: %s %d", __func__, __LINE__);
		ret = 1;
		goto out;
	}

	req->token_bin = calloc(1, normal_len << 1);
	if (!req->token_bin) {
		err("Out-of-Memory: %s %d", __func__, __LINE__);
		ret = 1;
		goto out;
	}

	// binary token is in token_in
	BIO_read(token_bio, token_in, normal_len);
	EVP_EncodeUpdate(&ectx, req->token_bin, &out, token_in, normal_len);
	total_len += out;
	EVP_EncodeFinal(&ectx, req->token_bin + total_len, &out);
	total_len += out;

	req->token_bin_len = total_len;

#ifdef DEBUG
{
	char token_file_name[128];
	sprintf(token_file_name, "/tmp/token-%08X-%08X-bin.dat", 
		req->userid, doorid);
	BIO *b = BIO_new_file(token_file_name, "wb");
	BIO_write(b, token_in, normal_len);
	BIO_free(b);
}
#endif

	free(token_in);

	dump("base64 token", req->token_bin, req->token_bin_len);
#endif

#ifdef DEBUG
{
	char token_file_name[128];
	sprintf(token_file_name, "/tmp/token-%08X-%08X.dat", 
		req->userid, doorid);
	BIO *b = BIO_new_file(token_file_name, "wb");
	BIO_write(b, req->token_bin, req->token_bin_len);
	BIO_free(b);
}
#endif

    ret = 0;

out:
	if (token_bio)
		BIO_free(token_bio);
	if (buffer)
		free(buffer);
	if (buffer_encrypt)
		free(buffer_encrypt);

	return ret;
}

int send_response(struct request *req)
{
	struct evbuffer *output = bufferevent_get_output(req->bev);
	struct cmd_hdr *hdr = (struct cmd_hdr *)req->buffer;
	struct resp_auth_ack_key *ack_key = (struct resp_auth_ack_key *)hdr->data;
	struct resp_auth_ack_remove_key *rm_key = (struct resp_auth_ack_remove_key *)hdr->data;
	int i;

	//generate_token
	for (i = 0; i < req->num_auth; i++) {
	    hdr->bType = CMD_RSP_AUTH_ACK_KEY;
	    hdr->wLength = sizeof(struct resp_auth_ack_key) + req->token_bin_len;
		ack_key->dwUserID = req->userid;
		ack_key->dwCommID = req->commid;
		ack_key->dwDoorID = req->auth_doorid[i];
		generate_token(req, req->auth_doorid[i]);
		strcpy((char *)ack_key->strDoorDesc, "THIS IS A SAMPLE");
		memcpy(ack_key->strKeyToken, req->token_bin, req->token_bin_len);

		dbg("%s %d: send auth %d/%d", __func__, __LINE__, i, req->num_auth);
		// use user pubkey to encrypt token
		evbuffer_add(output, hdr, CMD_HDR_LEN + hdr->wLength);
	}

	if (req->num_deauth) {
		hdr->bType = CMD_RSP_AUTH_RM_KEY;
		hdr->wLength = sizeof(*rm_key) + req->num_deauth * sizeof(uint32_t);

		rm_key->bDeAuthNum = req->num_deauth;
		rm_key->dwCommID = req->commid;
		memcpy(rm_key->dwDoorID, req->deauth_doorid, req->num_deauth);

		dbg("%s %d: send deauth %d", __func__, __LINE__, req->num_deauth);
		bufferevent_write(req->bev, hdr, CMD_HDR_LEN + hdr->wLength);
	}

	return 0;
}

static void handle_auth_req(struct request *req, void *_auth_req, uint32_t len)
{
	struct req_auth_req *auth_req = _auth_req;
	struct evbuffer *output = bufferevent_get_output(req->bev);
	struct cmd_hdr *hdr = (struct cmd_hdr *)req->buffer;
	struct resp_auth_enq *resp = (struct resp_auth_enq *)hdr->data;
	//int publen = len - sizeof(struct req_auth_req);

	err("%s %d", __func__, __LINE__);

	req->userid = auth_req->dwUserID;
	memcpy(req->bmac, auth_req->strBTAddress, BT_ADDR_LEN);
	memcpy(req->wmac, auth_req->strMACAddress, WIFI_ADDR_LEN);
	memcpy(req->imei, auth_req->strIMEI, IMEI_LEN);
	//req->pubkey_len = publen;
	//req->pubkey = malloc(req->pubkey_len);
	//memcpy(req->pubkey, auth_req->strPubKey, publen);
	//err("%s dump phone pub key", __func__);
	//dump(__func__, req->pubkey, publen);

	req->status = AUTH_ST_QUEUED;

	hdr->bType = CMD_RSP_AUTH_ENQ;
	hdr->wLength = sizeof(struct resp_auth_enq);
	/* TODO: abort connection if bResult == 0 */
	resp->bResult = 1;
	resp->bCause = 0;

	evbuffer_add(output, req->buffer, CMD_HDR_LEN + sizeof(struct resp_auth_enq));

#ifdef UNIT_TEST
	/* generate token */
    generate_token(req);
    req->status = AUTH_ST_KEY_GEND;
#endif
}

static void handle_auth_poll(struct request *req, void *_auth_poll, uint32_t len)
{
	//struct req_auth_poll *auth_poll = _auth_poll;
	//struct evbuffer *output;
	//struct resp_auth_ack_key *resp;
	//struct evbuffer *out = bufferevent_get_output(req->bev);

    if (req->status == AUTH_ST_KEY_GEND || req->status == AUTH_ST_KEY_REMOVED) {
        send_response(req);
    } else if (req->status == AUTH_ST_QUEUED) {
		struct cmd_hdr *hdr = (struct cmd_hdr *)req->buffer;
		struct resp_auth_ack_wait *resp = (struct resp_auth_ack_wait *)hdr->data;

		resp->bQueueStatus = 1;	//TODO: the number of waiting
		hdr->bType = CMD_RSP_AUTH_ACK_WAIT;
		hdr->wLength = sizeof(*resp);

		bufferevent_write(req->bev, hdr, CMD_HDR_LEN + sizeof(*resp));
    }
}

static void key_read_cb(struct bufferevent *bev, void *ctx)
{
	struct request *req = ctx;
	size_t datlen;
	struct cmd_hdr *hdr;

	/* This callback is invoked when there is data to read on bev. */
	struct evbuffer *input = bufferevent_get_input(bev);

	// make sure the data are in linear memory
	hdr = (struct cmd_hdr *)evbuffer_pullup(input, CMD_HDR_LEN);
	if (!hdr)
		//not enough data in the buffer
		return;

	datlen = hdr->wLength;
	if (datlen) {
		hdr = (struct cmd_hdr *)evbuffer_pullup(input, CMD_HDR_LEN + datlen);
		if (!hdr)
			//not enough data in the buffer
			return;
	}

	dump(__func__, hdr, CMD_HDR_LEN + datlen);

	switch (hdr->bType) {
	case CMD_REQ_AUTH_REQ:
		handle_auth_req(req, hdr->data, hdr->wLength);
		break;
	case CMD_REQ_AUTH_POLL:
		handle_auth_poll(req, hdr->data, hdr->wLength);
		break;
	}

	evbuffer_drain(input, CMD_HDR_LEN + datlen);
}

void request_done(struct request *req)
{
	if (req) {
		delete_from_request_list(req);
		if (req->pubkey)
			free(req->pubkey);
		if (req->auth_doorid)
			free(req->auth_doorid);
		if (req->deauth_doorid)
			free(req->deauth_doorid);
		if (req->token_bin)
			free(req->token_bin);
		free(req);
	}
}

static void key_event_cb(struct bufferevent *bev, short events, void *ctx)
{
	struct request *req = ctx;

	if (events & BEV_EVENT_ERROR)
		err("%s Error from bufferevent", __func__);

	if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
		err("%s connection closed", __func__);
		request_done(req);
		bufferevent_free(bev);
	}
}

void accept_conn_cb(struct evconnlistener *listener,
		evutil_socket_t fd, struct sockaddr *addr, int socklen, void *ctx)
{
	/* We got a new connection! Set up a bufferevent for it. */
	struct event_base *base = evconnlistener_get_base(listener);
	struct bufferevent *bev = bufferevent_socket_new(
			base, fd, BEV_OPT_CLOSE_ON_FREE);

	struct request *req = (struct request *)calloc(1, sizeof(*req));

	if (!req){
		err("Out-of-Memory: %s %d", __func__, __LINE__);
		bufferevent_free(bev);
		return;
	}

	req->bev = bev;
	add_to_request_list(req);

	bufferevent_setcb(bev, key_read_cb, NULL, key_event_cb, req);

	bufferevent_enable(bev, EV_READ|EV_WRITE);
}

void accept_error_cb(struct evconnlistener *listener, void *ctx)
{
	struct event_base *base = evconnlistener_get_base(listener);
	int err = EVUTIL_SOCKET_ERROR();
	err("Got an error %d (%s) on the listener. Shutting down.",
			err, evutil_socket_error_to_string(err));

	event_base_loopexit(base, NULL);
}

int init_server_key(void)
{
	FILE *fp;
	int ret;

	fp = fopen(kcomm_privkey_path, "rb");
	if (!fp) {
		err("cannot open %s", kcomm_privkey_path);
		goto failed;
	}

	if (!(kcomm_privkey = PEM_read_RSAPrivateKey(fp, NULL, NULL, NULL))) {
		err("open server private key failed\n");
		goto failed;
	}

	ret = RSA_check_key(kcomm_privkey);

	if(ret != 1) {
		printf("Check RSA private key, key error!\n");
		goto failed;
	}

	fclose(fp);
	return 0;

failed:
	if (fp) fclose(fp);
	return 1;
}

/* serial port starts */
#if defined(SERIAL_TRANSPORT) || defined(ETHERNET_TRANSPORT)
static void handle_serial_req_boxid(struct bufferevent *bev, void *data, uint32_t len)
{
	struct cmd_hdr *hdr = (struct cmd_hdr *)serial_rsp_buf;
	struct resp_box_id *resp = (struct resp_box_id *)hdr->data;

	resp->dwBoxID = BOXID;
	resp->dwCommID = COMMID;
	resp->dwUkeyID = UKEYID;
	strcpy((char *)resp->magic, BOX_ID_MAGIC_CODE);	//TODO: use strncpy

	hdr->bType = CMD_RSP_BOX_ID;
	hdr->wLength = sizeof(*resp);

	bufferevent_write(bev, hdr, CMD_HDR_LEN + sizeof(*resp));
}

/* FIXME: memory may overflow if request_list is too large. */
static void handle_serial_req_auth_q(struct bufferevent *bev, void *data, uint32_t len)
{
	struct cmd_hdr *hdr = (struct cmd_hdr *)serial_rsp_buf;
	struct resp_box_auth_queue *resp = (struct resp_box_auth_queue *)hdr->data;
	struct resp_box_auth_item *item = resp->items;
	struct request *r;
	int count;

	count = 0;
	item = resp->items;
	dbg("%s %d, request_list %p", __func__, __LINE__, request_list);
	for (r = request_list; r; r = r->next) {
		item->dwUserId = r->userid;
		memcpy(item->strBTAddr, r->bmac, BT_ADDR_LEN);
		memcpy(item->strMACAddr, r->wmac, WIFI_ADDR_LEN);
		memcpy(item->strIMEI, r->imei, IMEI_LEN);

		count++;
		item++;
	}

	resp->bQSize = count;
	hdr->bType = CMD_RSP_BOX_AUTH_QUE;
	hdr->wLength = sizeof(*resp)+ count * sizeof(*item);

	bufferevent_write(bev, hdr, CMD_HDR_LEN + sizeof(*resp) + count * sizeof(*item));
}

static void handle_serial_auth_conf(struct bufferevent *bev, void *data, uint32_t len)
{
	struct cmd_hdr *hdr = (struct cmd_hdr *)serial_rsp_buf;
	struct resp_box_auth_confirm *resp = (struct resp_box_auth_confirm *)hdr->data;
	struct req_box_auth_confirm *req = data;
	struct request *r;

	err("%s req->dwUserID %08X", __func__, req->dwUserID);
	r = find_request(req->dwUserID);
	if (r) {
		int auth_count = req->bDoorAuthNum;
		int deauth_count = req->bDoorDeauthNum;
		uint8_t *pubkey = NULL;
		int pubkey_len;

		r->num_auth = auth_count;
		r->num_deauth = deauth_count;
		r->commid = req->dwCommID;
		dbg("found request, num_auth %d deauth %d", auth_count, deauth_count);
		if (auth_count) {
			/* have pubkey */
			pubkey = req->data + (auth_count + deauth_count) * sizeof(uint32_t);
			pubkey_len = len - sizeof(*req) - (auth_count + deauth_count) * sizeof(uint32_t);
			dump(__func__, pubkey, pubkey_len);

			r->pubkey_len = pubkey_len;
			r->pubkey = malloc(r->pubkey_len);
			if (!r->pubkey) {
				err("Out-of-Memory: %s %d", __func__, __LINE__);
			}
			memcpy(r->pubkey, pubkey, pubkey_len);
#if 0
			generate_token(r);
#endif

			r->status = AUTH_ST_KEY_GEND;
			r->auth_doorid = malloc(auth_count * sizeof(uint32_t));
			if (!r->auth_doorid)
				err("Out-of-Memory: %s %d", __func__, __LINE__);
			memcpy(r->auth_doorid, req->data, auth_count * sizeof(uint32_t));
		}

		if (deauth_count) {
			r->deauth_doorid = malloc(deauth_count * sizeof(uint32_t));
			if (!r->deauth_doorid)
				err("Out-of-Memory: %s %d", __func__, __LINE__);
			memcpy(r->deauth_doorid, req->data + auth_count * sizeof(uint32_t),
				deauth_count * sizeof(uint32_t));
			r->status = AUTH_ST_KEY_REMOVED;
		}

		resp->bAuthResult = AUTH_RESULT_SUCC;
	} else {
		resp->bAuthResult = AUTH_RESULT_UNSPEC;
	}

	// TODO: after user polled, then box acks pc??
	hdr->bType = CMD_RSP_BOX_AUTH_CONFIRM;
	hdr->wLength = sizeof(*resp);
	bufferevent_write(bev, hdr, CMD_HDR_LEN + sizeof(*resp));
}

int process_serial(struct bufferevent *bev, struct cmd_hdr *hdr)
{
	int type;
	uint32_t len;

	type = hdr->bType;
	len = hdr->wLength;

	switch (type) {
	case CMD_REQ_BOX_ID:
		handle_serial_req_boxid(bev, hdr->data, len);
		break;
	case CMD_REQ_BOX_AUTH_QUE:
		handle_serial_req_auth_q(bev, hdr->data, len);
		break;
	case CMD_REQ_BOX_AUTH_CONFIRM:
		handle_serial_auth_conf(bev, hdr->data, len);
		break;
	}

	return 0;
#if 0
	char *info;
	char *p, *q;
	char *user_id;
	struct request *req;
	int status = 0;

	// if not find an response header, just return.
	if (strncmp(resp, DONGLE_COMMAND, strlen(DONGLE_COMMAND)))
		return 0;

	info += strlen(DONGLE_COMMAND);
	p = strtok(info, ",");
	do {
		if (!strncmp(p, "user_id:", 8))
			user_id = p;
		else if (!strncmp(p, "status:", 7) && !strcmp(p + 7, "OK"))
			status = 1;
	} while(p = strtok(NULL, ","));

	// search for a request
	struct request *req = find_request(user_id);
	if (req) {
		if (status) {
			BIO *token;
			struct evbuffer *output = bufferevent_get_output(req->bev);

			// check all information are ready
			token = BIO_new(BIO_s_mem());
			generate_token(req, token);

			// encrypt by user public key
			send_response(req, token, output);

			BIO_free(token);

			return 0;
		}
	}

	return 0;

	err("%s", resp);
	return 0;
#endif
}


static void serial_read_cb(struct bufferevent *bev, void *ctx)
{
	size_t datalen;
	struct cmd_hdr *hdr;

	/* This callback is invoked when there is data to read on bev. */
	struct evbuffer *input = bufferevent_get_input(bev);
	//struct evbuffer *output = bufferevent_get_output(bev);
	//evbuffer_get_length(st struct evbuffer * buf)

	/* make sure hdr + data are in linear memory space. */
	hdr = (struct cmd_hdr *)evbuffer_pullup(input, CMD_HDR_LEN);
	if (!hdr)
		//not enough data in the buffer
		return;

	datalen = hdr->wLength;
	if (datalen) {
		hdr = (struct cmd_hdr *)evbuffer_pullup(input, CMD_HDR_LEN + datalen);
		if (!hdr)
			//not enough data in the buffer
			return;
	}

	dump(__func__, hdr, CMD_HDR_LEN + datalen);
	process_serial(bev, hdr);
	evbuffer_drain(input, CMD_HDR_LEN + datalen);

#if 0
	// if data start with <DONGLE_COMMAND><RESPONSE>
	// and the data is ok, then ....
	char *request_line;
	size_t len;
	struct evbuffer *input = bufferevent_get_input(bev);
	
	// parse data
	for (;;) {
		// evbuffer_readln will return a line without CRLF.
		request_line = evbuffer_readln(input, &len, EVBUFFER_EOL_ANY);

		if (!request_line) {
			//err("line has not arrived yet");
			break;
		}

		/* TODO: do processing */
		process_serial(request_line, len);

		free(request_line);
	}
#endif
}

static void serial_event_cb(struct bufferevent *bev, short events, void *ctx)
{
	//struct client *cl = ctx;

	if (events & BEV_EVENT_ERROR)
		err("Error from bufferevent");

	if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
		err("serial port connection closed");

#ifdef ETHERNET_TRANSPORT
		bufferevent_free(bev);
#endif
	}
}


int setup_serial(struct auth_context *context)
{
	int fd;
	struct termios options;

	fd = open(DEFAULT_SERIAL_PORT, O_RDWR | O_NOCTTY | O_NDELAY);
	if (fd < 0) {
		perror("Unable to open " DEFAULT_SERIAL_PORT);
		return 1;
	}

	evutil_make_socket_nonblocking(fd);

	// get the current options for the port
	tcgetattr(fd, &options);
	cfmakeraw(&options);
	cfsetispeed(&options, B115200);
	cfsetospeed(&options, B115200);

	if (tcsetattr(fd, TCSAFLUSH, &options) < 0) {
		err("failed to configure serial port");
		return 1;
	}

	context->serial_bev = bufferevent_socket_new(context->base, fd, BEV_OPT_CLOSE_ON_FREE);
	bufferevent_setcb(context->serial_bev, serial_read_cb, NULL, serial_event_cb, context);
	bufferevent_enable(context->serial_bev, EV_READ | EV_WRITE);

	context->serial_fd = fd;

	return 0;
}

void serial_accept_conn_cb(struct evconnlistener *listener,
		evutil_socket_t fd, struct sockaddr *addr, int socklen, void *ctx)
{
	/* We got a new connection! Set up a bufferevent for it. */
	struct event_base *base = evconnlistener_get_base(listener);
	struct bufferevent *bev = bufferevent_socket_new(
			base, fd, BEV_OPT_CLOSE_ON_FREE);

	bufferevent_setcb(bev, serial_read_cb, NULL, serial_event_cb, ctx);

	bufferevent_enable(bev, EV_READ|EV_WRITE);
}

int setup_eth_transport(struct auth_context *context)
{
	struct evconnlistener *listener;
	struct sockaddr_in sin;

	memset(&sin, 0, sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_addr.s_addr = htonl(0);
	sin.sin_port = htons(8890);

	listener = evconnlistener_new_bind(context->base, serial_accept_conn_cb, context,
			LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, KEY_SERVER_BACKLOG,
			(struct sockaddr *)&sin, sizeof(sin));

	if (!listener) {
		err("Couldn't create listener");
		return 1;
	}

	evconnlistener_set_error_cb(listener, accept_error_cb);

	return 0;
}
#endif

static int usage(const char *name)
{
	err("Usage: %s\n"
		"    -p: port\n"
		"    -k: community private key path\n"
		"    -h: help\n", 
		name);
	return 0;
}

int main(int argc, char *argv[])
{
	int ch;
	int port = KEY_SERVER_PORT;
	struct auth_context *context;
	struct evconnlistener *listener;
	struct sockaddr_in sin;

	strcpy(kcomm_privkey_path, COMMUNITY_RSA_PRIVATE_KEY_NAME);

	while ((ch = getopt(argc, argv, "p:k:h")) != -1) {
		switch(ch) {
		case 'p':
			port = atoi(optarg);
			break;
		case 'k':
			strcpy(kcomm_privkey_path, optarg);
			break;
		case 'h':
			/* fall through */
		default:
			return usage(argv[0]);
		}
	}

	if (!(context = calloc(1, sizeof(*context)))) {
		err("Out-of-Memory");
		return 1;
	}

	if (!(context->base = event_base_new())) {
		err("Couldn't open event base");
		return 1;
	}

	if (init_server_key()) {
		err("Couldn't init server private key");
		return 1;
	}

	memset(&sin, 0, sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_addr.s_addr = htonl(0);
	sin.sin_port = htons(port);

	listener = evconnlistener_new_bind(context->base, accept_conn_cb, NULL,
			LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, KEY_SERVER_BACKLOG,
			(struct sockaddr *)&sin, sizeof(sin));

	if (!listener) {
		err("Couldn't create listener");
		return 1;
	}

	evconnlistener_set_error_cb(listener, accept_error_cb);

	err("key server starting on port %d", port);

#ifdef SERIAL_TRANSPORT
	if (setup_serial(context)) {
		err("Couldn't setup serial port");
		return 1;
	}
#endif

#ifdef ETHERNET_TRANSPORT
	if (setup_eth_transport(context)) {
		err("Couldn't setup ethernet transport");
		return 1;
	}
#endif

	event_base_dispatch(context->base);

	return 0;
}

